#!/usr/bin/env python3

# Licensed under the Apache License, Version 2.0 or the MIT License.
# SPDX-License-Identifier: Apache-2.0 OR MIT
# Copyright Tock Contributors 2023.

"""
Script to generate interrupt handler stubs for x86 architecture.

usage: ./gen_x86_handler_stubs.py [path/to/handler_stubs.rs]

If path/to/handler_stubs.rs is omitted, then a default path is used.
"""

from io import TextIOBase
import os.path
import sys

DEFAULT_STUB_FILE = os.path.normpath(
    os.path.join(
        os.path.dirname(__file__),
        "../../arch/x86/src/interrupts/handler_stubs.rs",
    )
)

def has_error_code(int_num: int) -> bool:
    """
    Return whether an error code would be pushed on the stack for the specified int_num.

    For certain interrupt numbers, the x86 CPU will automatically push an error code onto the stack
    containing additional information about the cause of the interrupt. This is only done for
    certain interrupts; for all others, nothing is pushed.

    This function returns `True` if `int_num` is one of the interrupt numbers for which an error
    code will be pushed. Otherwise, this function returns `False`.
    """

    return int_num in (8, 10, 11, 12, 13, 14, 17, 21)

def gen_stubs(f: TextIOBase):
    """Generate interrupt handler stubs and write the resulting assembly code to `f`."""

    f.write("// Licensed under the Apache License, Version 2.0 or the MIT License.\n")
    f.write("// SPDX-License-Identifier: Apache-2.0 OR MIT\n")
    f.write("// Copyright Tock Contributors 2024.\n")
    f.write("\n")
    f.write("// Interrupt handler stubs for x86 architecture\n")
    f.write("//\n")
    f.write("// Generated by gen_x86_int_stubs.py\n")
    f.write("\n")
    f.write("use core::arch::global_asm;\n\n")
    f.write("global_asm!(\n    \"\n")
    f.write(".section .text\n")

    for i in range(256):
        f.write("\n")

        # Alignment ensures each stub is a predictable size/offset
        f.write(".align 16\n")

        # Only need to export symbols for the first two stubs. Using these we can compute the
        # address of all the rest.
        if i in (0, 1):
            f.write(f".global handler_stub_{i}\n")
            f.write(f"handler_stub_{i}:\n")

        # On x86, an error code is automatically pushed onto the stack for some exceptions. For all
        # other interrupt numbers, we manually push a placeholder onto the stack. This simplifies
        # the common interrupt handler.
        if not has_error_code(i):
            f.write("  push 0\n")

        # Push the actual number of the interrupt onto the stack. Without this, there is no other
        # way in the x86 ISA to determine the current interrupt number.
        f.write(f"  push {i}\n")

        # Jump to the common interrupt handler entry point
        f.write("  jmp handler_entry\n")
    f.write("\"\n);\n")

if __name__ == "__main__":
    if len(sys.argv) == 1:
        stub_file_path = DEFAULT_STUB_FILE
    elif len(sys.argv) == 2:
        stub_file_path = sys.argv[1]
    else:
        print(__doc__)
        sys.exit(-1)

    print(f"Writing handler stubs to file: {stub_file_path}")

    with open(stub_file_path, "w") as f:
        gen_stubs(f)
